home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The 640 MEG Shareware Studio 2
/
The 640 Meg Shareware Studio CD-ROM Volume II (Data Express)(1993).ISO
/
basic
/
qbredir.zip
/
QBREDIR.ASM
next >
Wrap
Assembly Source File
|
1992-03-02
|
18KB
|
434 lines
; Quick Basic Redirected File Display
;
;
; ---- A modified version of the original PD program REDVIEW.ASM ----
; by Alexandr Novy and Peter Horak of Prague, Czechoslovakia
;
;
; ---- This Public Domain Adaptation for QuickBasic Written By:
;
; Peter R. Barnes
; Brentwood, TN
; March, 1992
;
;
; ---- To assemble the object module with TASM, use the command:
;
; TASM QBREDIR ;I don't have MASM,
; ;but I'm sure the
; ;syntax is the same
;
;
; ---- To use in a QuickBasic program, compile your program
; and link the module to your code, for example:
;
; BC /options YOURPROG; ;any options you want
;
; LINK /linkoptions YOURPROG + QBREDIR;
;
;
;
; ---- To call the routines in your program, use the following:
;
; CALL QFRedSet (FlagIntegerVariable%)
;
; CALL QFRedOff
;
;
; --- IMPORTANT: READ THE QBREDIR.DOC FILE TO SEE HOW TO
; PROPERLY USE THESE ROUTINES!! THE ROUTINES
; INSTALL A CUSTOM INTERRUPT HANDLER WHICH
; MUST BE TREATED WITH CARE!!
;
; ---- NOTE: CANNOT BE USED IN THE QB ENVIRONMENT (SEE
; QBREDIR.DOC)
;
;
; ---- I have added some comments to help explain the code as
; it applies to QuickBasic, just for general (and my own)
; reference. Some of the stuff is pretty elementary, so if
; you are an assembly language wiz, just skip it; it's there
; for the rest of us.
;
;
;********************************************************************
;********************************************************************
;
; Make our procedure names available to external programs
;
public QFRedSet, QFRedOff
;
;
;********************************************************************
;
; All Basic modules are assembled using the Medium
; memory model, so we must add the proper assembler directives
;
;
.MODEL Medium, Basic ;always, for QuickBasic
;
.CODE ;make the code segment our default
;
;
;
; We will let QBasic and TASM set the type of procedure, and we will
; declare the module using the TASM simplified format
;
;
QFRedSet proc uses si di es ds
ARG flag:PTR = ARGLEN
;
;
; That tells TASM to add code for a Basic procedure call, save
; si,di,es,ds pointers, and that we are passing to the procedure a
; two-byte pointer to the INTEGER (notice that I capitalized that --
; three guesses why) variable /flag/, consisting of ARGLEN number of
; bytes -- the QuickBasic 'call' automatically pushes that address onto
; the stack and TASM calculates the correct offset to those stack
; bytes when it assembles the code. All parameter addresses passed by
; QuickBasic are near references (offset only, 1 word or 2 bytes)
; unless you use the keyword SEG for the passed variable, which tells
; QuickBasic to pass the address as a far reference (segment:offset,
; 2 words or 4 bytes).
; ---------
; TASM knows from our 'Basic' directive that the variable we pass will
; be a 2-byte address, and that Basic always makes far calls, leaving a full,
; 2-word return address as the last 4 bytes on the stack; the variable
; address, next on the stack, will be at [B(ase)P(ointer)+6] from the
; present BP value (+ because the stack grows downward in memory).
; That's also how TASM knows that we need a far return, not a near
; return, and also how many bytes to discard, after it pops that QB
; return address, to restore the stack after the procedure ends --
; i.e. 2 bytes for the variable near address...
;
; push bp ;TASM automatically
; mov bp,sp ;adds the code to save the B(ase)
; ;P(ointer) to the stack in the stack
; ;segment, and set up a new stack
; ;that starts at the end of the old
; ;one pointed to by the current
; ;S(tack)P(ointer) -- i.e.
; New bp = Old bp + sp
; ;Actually, our new stack will begin
; ;at New bp + sp, but this will keep
; ;us away from the old stack for
; ;all values of sp, in case sp gets
; ;set to a lower value -- we will
; ;never get lower than the new bp.
; ;Of course, we have to be careful
; ;to preserve that sp value in our
; ;code by always 'popping' the same
; ;number of bytes that we 'push'
; ;onto the stack.
;
;
; ;This is the code that is added
; ;by TASM when it sees the 'Basic'
; push si ;and the 'uses' directives, so we
; push di ;don't need the explicit statements.
; push es ;The address of /flag/ is placed on
; push ds ;the stack first, and ARGLEN tells
; ;TASM how many bytes to discard, when
; ;we return from the call, to get
; ;back to the original stack before
; ;the call.
;
;
;********************************************************************
;
;
;
; The first thing we do is grab and save the address of the QB variable
; that was passed to us, on the stack, by QuickBasic when it made the
; call; remember that TASM knows that [flag] means [BP+6]. In this
; call, we passed the 2-byte offset address of the variable in the
; ds data segment, so the 'word ptr [flag]' will have the 2-byte word
; for the offset moved into ax; then the 2-byte word for the ds data
; segment, whatever it is, will be stored. Both of those values are
; copied into /flagadr/ by the following 3 mov instructions:
;
;
mov ax,word ptr [flag] ;save the offset of passed
mov cs:flagadr,ax ;integer variable /flag/ in
; ;memory location /flagadr/
;
;
; That saves the offset that we passed, now we save the data segment
; pointer that is in ds at the time of the call. Since QuickBasic
; only has one data segment for integer variables, ds will already be
; pointing to the segment containing our /flag/ variable.
;
; We could pass the segment pointer when we made the call from
; QuickBasic by preceding the variable name with the SEG keyword; in
; that situation, QuickBasic would push the full 4-byte address onto
; the stack. We could also use the CALLS statement to call this
; routine, instead of CALL, because that statement always passes the
; full address of parameters. But neither of those actions are
; required, because QuickBasic only has one data segment
;
; The following mov instruction saves our data segment for DOS to
; use to find our variable when it executes the interrupt routine:
;
;
mov cs:flagadr+2,ds ;save the segment pointer
; ;of passed integer variable
; ;/flag/ in second word
; ;of /flagadr/ --
; ;that's what DOS needs when
; ;the interrupt is called
;
;
; Next step is flagcheck to prevent multiple calls to this routine
; if it has not been reset. Without this check, a second call would
; save our new handler address as the original.
;
; Check to see if we have already installed the vector to the
; interrupt handler routine. If we have, we don't want to do it
; again, because we will lose the original vector
;
mov al,cs:[IntSetFlag] ;get flag to see if handler
cmp al,0 ;is already installed
jne EndSet ;if set, we have installed
; ;so go to exit
;
mov al, 1 ;else set the flag
mov cs:[IntSetFlag],al
;
;
; ;pick up original vector contents
;
mov ax,3521H ;for interrupt 21H (MS-DOS Services
int 21H ;Function Request handler)
;
mov cs:Old21Offset,bx ;save the original vector
mov cs:Old21Segment,es ;in our data area
;
;
; ;a call to DOS services INT21H for
; ;the SET INTERRUPT function 25xxH
; ;requires ds:dx to be set to the
; ;interrupt address, so the next two
; ;statements set the ds segment
; ;equal to the cs segment, where
; ;our new handler procedure is
; ;located
;
;
push cs ;push our code segment onto stack
pop ds ;pop it back off to set ds
; ;i.e. sets ds=cs
;
mov dx,offset QDualDisp ;load offset of our Int 21 handler
mov ax,02521H ;and reset vector to point to
int 21H ;our procedure
;
;
; pop ds ;restore registers and
; pop es ;return to our QB program
; pop di
; pop si ;NOTE--TASM automatically adds
; pop bp ;the code to restore registers
;
EndSet: ret
;
; actually, it's 'ret [ARGLEN]' ;TASM automatically makes the
; ;return discard the /flag/ address
; ;bytes from the stack--the number
; ;of bytes to discard was set by
; ;the ARGLEN directive
;
;
;
; Following is our new interrupt handler which is called by
; MS-DOS whenever an Interrupt 21 MS-DOS Services Request
; is detected. The procedure first checks our enable flag
; to determine if we want the dual output activated; the address of
; this flag was passed by the QB program during the call to
; the QFRedSet function. If the flag is not set, we just skip our
; handler and proceed to the regular interrupt 21 routine.
;
; The handler then checks to see what type of service is being requested.
; If it is a call to the DOS routines to output a character or a string
; to STDOUT, the standard DOS output device, which is redirectible and
; is always designated with file handle 1, then our procedure essentially
; duplicates the routine by sending the output to STDERR, the standard
; DOS error output device, which is always the display, file handle 2,
; and whose output cannot be redirected. Then the handler exits to the
; normal Interrupt 21 procedure to let it process the original call.
;
; Note that the routine is part of the QFRedSet procedure, but it is never
; executed when that procedure is called by the QB program, because that
; procedure ends at the 'ret' opcode at EndSet, above.
;
QDualDisp:
pushf ;save everything we can --
push ax ;i.e. all registers and flags
push bx
push cx ;note that we save the flags
push dx ;even though DOS did the same
push bp ;thing when it called the
push si ;interrupt routine
push di
push bx ;save entering register values
push ds
mov bx,cs:flagadr ;get address of flag value passed
mov ds,cs:flagadr+2 ;from our QB program
mov al,ds:bx ;get flag value into al
pop ds ;retrieve register values
pop bx
or al, al ;see if value was zero
je EndOur21 ;yes, don't do anything, go
;to the regular interrupt 21 routine
cmp ah,02 ;was it a call to write character to STDOUT
jne Int9Chk ;no, jump to next check
push ds ;yes, save entering data segment value
push cs ;set ds=cs
pop ds
mov cs:character,dl ;save the character to write in character
mov ah,40h ;set up for int 21 function 40h
;to write character to output device
mov bx,2 ;set file handle=2 for STDERR
mov dx,OFFSET character ;get the pointer to the character
mov cx,1 ;one character to be written
pushf ;save current flag status
call dword ptr cs:[Old21];use old interrupt routine to output
;the character to the display,
;it will pop the flags saved on stack
;when it returns, because it ends with
;iret, since it is an interrupt routine
pop ds ;retrieve our character
jmp short EndOur21 ;exit to old routine to write the character
;to redirected output
Int9Chk:
cmp ah,09 ;was it a call to write string to output
jne Int40Chk ;no, go to next check
Chrloop:
mov si,dx ;yes, set index
cmp byte ptr ds:[si],'$';are we at the end of the string
je End2109 ;yes, go to finish
mov ah,40h ;no, set up for int 21 function 40h
mov bx,2 ;output to STDERR
mov cx,1
push dx
pushf
call dword ptr cs:[Old21];write the character as above
pop dx
inc dx ;bump index to next character
jmp short Chrloop ;loop until done
End2109:
jmp short EndOur21 ;exit to old routine to write the character
;to redirected output
Int40Chk:
cmp ah,40h ;was it a call to write to file or device
jne EndOur21 ;no, done, exit to regular int 21
cmp bx,1 ;yes, then was it a call to STDOUT
jne EndOur21 ;no, done, exit
push ds ;yes, save character
mov bx,2 ;set file handle for STDERR
pushf
call dword ptr cs:[Old21] ;use old interrupt routine to output
pop ds ;retrieve character
EndOur21:
;retrieve registers
pop di ;this resets registers
pop si ;and flags to exactly
pop bp ;where they were when we
pop dx ;entered our custom handler
pop cx
pop bx
pop ax
popf
;
;
jmp dword ptr cs:[Old21] ;go to regular int 21 routine
;
;
;
;
QFRedSet endp
;
;
;
; The subroutine QFRedOff is called by the QB program to
; return the MS-DOS interrupt vector 21H to its' original state.
; The subroutine is used in the form:
;
; CALL QFRedOff()
;
;
QFRedOff proc uses si di es ds ;Restore MS-DOS Services
; ;interrupt vector 21H
; push bp ;to its' original state
; mov bp,sp
; push ds ;same code added as above
; etc.
;
;
; First we must check our IntSetFlag to see if the
; vectors to our handler were installed; if not, we
; do not want to reset them here, either
;
;
mov al,cs:[IntSetFlag] ;Get our inhibit flag
cmp al,1 ;see if it is set
jne EndOff ;no, go to exit
;else, reset vector
;
;
mov dx,cs:Old21Offset ;Set interrupt 21H MS-DOS
mov ds,cs:Old21Segment ;Services Request
mov ax,02521H ;back to its' original vector
int 21H
;
;
mov al, 0 ;reset the flag to enable
mov cs:[IntSetFlag],al ;the QFRedSet routine
;
; pop ds ;restore registers and
; pop bp ;return to QB program --
; etc. ;same exit code added as above
;
EndOff: ret
;
QFRedOff endp
;
;
;
IntSetFlag db 0 ;this flag inhibits QFRedSet operation
; ;after it has redirected vector
; ;until QFRedOff resets it, and does
; ;the same for QFRedOff if QFRedSet
; ;has not redirected vector
;
flagadr dw 0,0 ;Long address of QB program's
; ;display redirected output flag --
; ;note that each '0' is actually 2 bytes
;
Old21 Label Dword
Old21Offset dw ? ;Original contents of MS-DOS
Old21Segment dw ? ;Interrupt 21H vector
;
;
character DB ? ;storage for output character
;
end